/*******************************************************************************
 * Copyright (c) 2000, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.jdt.internal.ui.text.correction;

import org.eclipse.che.ide.ext.java.shared.dto.Problem;
import org.eclipse.che.jface.text.contentassist.ICompletionProposal;
import org.eclipse.che.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.IQuickAssistProcessor;
import org.eclipse.jdt.ui.text.java.IQuickFixProcessor;
import org.eclipse.jface.text.source.Annotation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class JavaCorrectionProcessor implements org.eclipse.che.jface.text.quickassist.IQuickAssistProcessor {

	private static final String QUICKFIX_PROCESSOR_CONTRIBUTION_ID= "quickFixProcessors"; //$NON-NLS-1$
	private static final String QUICKASSIST_PROCESSOR_CONTRIBUTION_ID= "quickAssistProcessors"; //$NON-NLS-1$

	private static ContributedProcessorDescriptor[] fgContributedAssistProcessors= null;
	private static ContributedProcessorDescriptor[] fgContributedCorrectionProcessors= null;

	private static ContributedProcessorDescriptor[] getProcessorDescriptors(String contributionId, boolean testMarkerTypes) {
//		IConfigurationElement[] elements= Platform.getExtensionRegistry().getConfigurationElementsFor(JavaPlugin.ID_PLUGIN, contributionId);
		ArrayList<ContributedProcessorDescriptor> res= new ArrayList<>();
//
//		for (int i= 0; i < elements.length; i++) {
//			ContributedProcessorDescriptor desc= new ContributedProcessorDescriptor(elements[i], testMarkerTypes);
//			IStatus status= desc.checkSyntax();
//			if (status.isOK()) {
//				res.add(desc);
//			} else {
//				JavaPlugin.log(status);
//			}
//		}

		if(contributionId == QUICKFIX_PROCESSOR_CONTRIBUTION_ID) {
			res.add(new ContributedProcessorDescriptor(new QuickFixProcessor(), testMarkerTypes));
		} else {
			res.add(new ContributedProcessorDescriptor(new QuickAssistProcessor(), testMarkerTypes));
			res.add(new ContributedProcessorDescriptor(new AdvancedQuickAssistProcessor(), testMarkerTypes));
		}
		return res.toArray(new ContributedProcessorDescriptor[res.size()]);
	}

	private static ContributedProcessorDescriptor[] getCorrectionProcessors() {
		if (fgContributedCorrectionProcessors == null) {
			fgContributedCorrectionProcessors= getProcessorDescriptors(QUICKFIX_PROCESSOR_CONTRIBUTION_ID, true);
		}
		return fgContributedCorrectionProcessors;
	}

	private static ContributedProcessorDescriptor[] getAssistProcessors() {
		if (fgContributedAssistProcessors == null) {
			fgContributedAssistProcessors= getProcessorDescriptors(QUICKASSIST_PROCESSOR_CONTRIBUTION_ID, false);
		}
		return fgContributedAssistProcessors;
	}

	public static boolean hasCorrections(ICompilationUnit cu, int problemId, String markerType) {
		ContributedProcessorDescriptor[] processors= getCorrectionProcessors();
		SafeHasCorrections collector= new SafeHasCorrections(cu, problemId);
		for (int i= 0; i < processors.length; i++) {
			if (processors[i].canHandleMarkerType(markerType)) {
				collector.process(processors[i]);
				if (collector.hasCorrections()) {
					return true;
				}
			}
		}
		return false;
	}

//	public static boolean isQuickFixableType(Annotation annotation) {
//		return (annotation instanceof IJavaAnnotation || annotation instanceof SimpleMarkerAnnotation) && !annotation.isMarkedDeleted();
//	}


	public static boolean hasCorrections(Annotation annotation) {
//		if (annotation instanceof IJavaAnnotation) {
//			IJavaAnnotation javaAnnotation= (IJavaAnnotation) annotation;
//			int problemId= javaAnnotation.getId();
//			if (problemId != -1) {
//				ICompilationUnit cu= javaAnnotation.getCompilationUnit();
//				if (cu != null) {
//					return hasCorrections(cu, problemId, javaAnnotation.getMarkerType());
//				}
//			}
//		}
//		if (annotation instanceof SimpleMarkerAnnotation) {
//			return hasCorrections(((SimpleMarkerAnnotation) annotation).getMarker());
//		}
//		return false;
		throw new UnsupportedOperationException("hasCorrections");
	}

//	private static boolean hasCorrections(IMarker marker) {
//		if (marker == null || !marker.exists())
//			return false;
//
//		IMarkerHelpRegistry registry= IDE.getMarkerHelpRegistry();
//		return registry != null && registry.hasResolutions(marker);
//	}

	public static boolean hasAssists(IInvocationContext context) {
		ContributedProcessorDescriptor[] processors= getAssistProcessors();
		SafeHasAssist collector= new SafeHasAssist(context);

		for (int i= 0; i < processors.length; i++) {
			collector.process(processors[i]);
			if (collector.hasAssists()) {
				return true;
			}
		}
		return false;
	}

//	private JavaCorrectionAssistant fAssistant;
	private String fErrorMessage;

	/*
	 * Constructor for JavaCorrectionProcessor.
	 */
	public JavaCorrectionProcessor(/*JavaCorrectionAssistant assistant*/) {
//		fAssistant= assistant;
//		fAssistant.addCompletionListener(new ICompletionListener() {
//
//			public void assistSessionEnded(ContentAssistEvent event) {
//				fAssistant.setStatusLineVisible(false);
//			}
//
//			public void assistSessionStarted(ContentAssistEvent event) {
//				fAssistant.setStatusLineVisible(true);
//				fAssistant.setStatusMessage(getJumpHintStatusLineMessage());
//			}
//
//			public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
//				if (proposal instanceof IStatusLineProposal) {
//					IStatusLineProposal statusLineProposal= (IStatusLineProposal)proposal;
//					String message= statusLineProposal.getStatusMessage();
//					if (message != null) {
//						fAssistant.setStatusMessage(message);
//						return;
//					}
//				}
//				fAssistant.setStatusMessage(getJumpHintStatusLineMessage());
//			}
//
//			private String getJumpHintStatusLineMessage() {
//				if (fAssistant.isUpdatedOffset()) {
//					String key= getQuickAssistBinding();
//					if (key == null)
//						return CorrectionMessages.JavaCorrectionProcessor_go_to_original_using_menu;
//					else
//						return Messages.format(CorrectionMessages.JavaCorrectionProcessor_go_to_original_using_key, key);
//				} else if (fAssistant.isProblemLocationAvailable()) {
//					String key= getQuickAssistBinding();
//					if (key == null)
//						return CorrectionMessages.JavaCorrectionProcessor_go_to_closest_using_menu;
//					else
//						return Messages.format(CorrectionMessages.JavaCorrectionProcessor_go_to_closest_using_key, key);
//				} else
//					return ""; //$NON-NLS-1$
//			}
//
//			private String getQuickAssistBinding() {
//				final IBindingService bindingSvc= (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class);
//				return bindingSvc.getBestActiveBindingFormattedFor(ITextEditorActionDefinitionIds.QUICK_ASSIST);
//			}
//		});
	}

	/*
	 * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int)
	 */
	public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext quickAssistContext) {
//		ISourceViewer viewer= quickAssistContext.getSourceViewer();
//		int documentOffset= quickAssistContext.getOffset();
//
////		IEditorPart part= fAssistant.getEditor();
//
//		ICompilationUnit cu= JavaUI.getWorkingCopyManager().getWorkingCopy(part.getEditorInput());
//		IAnnotationModel model= JavaUI.getDocumentProvider().getAnnotationModel(part.getEditorInput());
//
//		AssistContext context= null;
//		if (cu != null) {
//			int length= viewer != null ? viewer.getSelectedRange().y : 0;
//			context= new AssistContext(cu, viewer, /*part, */documentOffset, length);
//		}
//
//		Annotation[] annotations= fAssistant.getAnnotationsAtOffset();
//
//		fErrorMessage= null;
//
//		ICompletionProposal[] res= null;
//		if (model != null && context != null && annotations != null) {
//			ArrayList<IJavaCompletionProposal> proposals= new ArrayList<IJavaCompletionProposal>(10);
//			IStatus status= collectProposals(context, model, annotations, true, !fAssistant.isUpdatedOffset(), proposals);
//			res= proposals.toArray(new ICompletionProposal[proposals.size()]);
//			if (!status.isOK()) {
//				fErrorMessage= status.getMessage();
//				JavaPlugin.log(status);
//			}
//		}
//
//		if (res == null || res.length == 0) {
//			return new ICompletionProposal[] { new ChangeCorrectionProposal(CorrectionMessages.NoCorrectionProposal_description, new
// NullChange(""), IProposalRelevance.NO_SUGGESSTIONS_AVAILABLE, null) }; //$NON-NLS-1$
//		}
//		if (res.length > 1) {
//			Arrays.sort(res, new CompletionProposalComparator());
//		}
//		return res;
		throw new UnsupportedOperationException("computeQuickAssistProposals");
	}

	public static IStatus collectProposals(IInvocationContext context, /*IAnnotationModel model, */List<Problem> annotations, boolean addQuickFixes, boolean addQuickAssists, Collection<IJavaCompletionProposal> proposals) {
		ArrayList<ProblemLocation> problems= new ArrayList<>();

		// collect problem locations and corrections from marker annotations
		for (Problem curr : annotations) {
				problems.add(new ProblemLocation(curr));
		}
//		for (int i= 0; i < annotations.length; i++) {
//			Annotation curr= annotations[i];
//			ProblemLocation problemLocation= null;
//			if (curr instanceof IJavaAnnotation) {
//				problemLocation= getProblemLocation((IJavaAnnotation) curr, model);
//				if (problemLocation != null) {
//					problems.add(problemLocation);
//				}
//			}
////			if (problemLocation == null && addQuickFixes && curr instanceof SimpleMarkerAnnotation) {
////				collectMarkerProposals((SimpleMarkerAnnotation) curr, proposals);
////			}
//		}
		MultiStatus resStatus= null;

		IProblemLocation[] problemLocations= problems.toArray(new IProblemLocation[problems.size()]);
		if (addQuickFixes) {
			IStatus status= collectCorrections(context, problemLocations, proposals);
			if (!status.isOK()) {
				resStatus= new MultiStatus(JavaPlugin.ID_PLUGIN, IStatus.ERROR, CorrectionMessages.JavaCorrectionProcessor_error_quickfix_message, null);
				resStatus.add(status);
			}
		}
		if (addQuickAssists) {
			IStatus status= collectAssists(context, problemLocations, proposals);
			if (!status.isOK()) {
				if (resStatus == null) {
					resStatus= new MultiStatus(JavaPlugin.ID_PLUGIN, IStatus.ERROR, CorrectionMessages.JavaCorrectionProcessor_error_quickassist_message, null);
				}
				resStatus.add(status);
			}
		}
		if (resStatus != null) {
			return resStatus;
		}
		return Status.OK_STATUS;
	}

//	private static ProblemLocation getProblemLocation(IJavaAnnotation javaAnnotation, IAnnotationModel model) {
//		int problemId= javaAnnotation.getId();
//		if (problemId != -1) {
//			Position pos= model.getPosition((Annotation) javaAnnotation);
//			if (pos != null) {
//				return new ProblemLocation(pos.getOffset(), pos.getLength(), javaAnnotation); // java problems all handled by the quick assist processors
//			}
//		}
//		return null;
//	}

//	private static void collectMarkerProposals(SimpleMarkerAnnotation annotation, Collection<IJavaCompletionProposal> proposals) {
//		IMarker marker= annotation.getMarker();
//		IMarkerResolution[] res= IDE.getMarkerHelpRegistry().getResolutions(marker);
//		if (res.length > 0) {
//			for (int i= 0; i < res.length; i++) {
//				proposals.add(new MarkerResolutionProposal(res[i], marker));
//			}
//		}
//	}

	private static abstract class SafeCorrectionProcessorAccess implements ISafeRunnable {
		private MultiStatus fMulti = null;
		private ContributedProcessorDescriptor fDescriptor;

		public void process(ContributedProcessorDescriptor[] desc) {
			for (int i = 0; i < desc.length; i++) {
				fDescriptor = desc[i];
				SafeRunner.run(this);
			}
		}

		public void process(ContributedProcessorDescriptor desc) {
			fDescriptor = desc;
			SafeRunner.run(this);
		}

		public void run() throws Exception {
			safeRun(fDescriptor);
		}

		protected abstract void safeRun(ContributedProcessorDescriptor processor) throws Exception;

		public void handleException(Throwable exception) {
			if (fMulti == null) {
				fMulti = new MultiStatus(JavaPlugin.ID_PLUGIN, IStatus.OK, CorrectionMessages.JavaCorrectionProcessor_error_status, null);
			}
			fMulti.merge(new Status(IStatus.ERROR, JavaPlugin.ID_PLUGIN, IStatus.ERROR, CorrectionMessages
					.JavaCorrectionProcessor_error_status,
									exception));
		}

		public IStatus getStatus() {
			if (fMulti == null) {
				return Status.OK_STATUS;
			}
			return fMulti;
		}

	}

	private static class SafeCorrectionCollector extends SafeCorrectionProcessorAccess {
		private final IInvocationContext                  fContext;
		private final Collection<IJavaCompletionProposal> fProposals;
		private       IProblemLocation[]                  fLocations;

		public SafeCorrectionCollector(IInvocationContext context, Collection<IJavaCompletionProposal> proposals) {
			fContext = context;
			fProposals = proposals;
		}

		public void setProblemLocations(IProblemLocation[] locations) {
			fLocations = locations;
		}

		@Override
		public void safeRun(ContributedProcessorDescriptor desc) throws Exception {
			IQuickFixProcessor curr = (IQuickFixProcessor)desc.getProcessor(fContext.getCompilationUnit(), IQuickFixProcessor.class);
			if (curr != null) {
				IJavaCompletionProposal[] res = curr.getCorrections(fContext, fLocations);
				if (res != null) {
					for (int k = 0; k < res.length; k++) {
						fProposals.add(res[k]);
					}
				}
			}
		}
	}

	private static class SafeAssistCollector extends SafeCorrectionProcessorAccess {
		private final IInvocationContext                  fContext;
		private final IProblemLocation[]                  fLocations;
		private final Collection<IJavaCompletionProposal> fProposals;

		public SafeAssistCollector(IInvocationContext context, IProblemLocation[] locations,
								   Collection<IJavaCompletionProposal> proposals) {
			fContext = context;
			fLocations = locations;
			fProposals = proposals;
		}

		@Override
		public void safeRun(ContributedProcessorDescriptor desc) throws Exception {
			IQuickAssistProcessor curr =
					(IQuickAssistProcessor)desc.getProcessor(fContext.getCompilationUnit(), IQuickAssistProcessor.class);
			if (curr != null) {
				IJavaCompletionProposal[] res = curr.getAssists(fContext, fLocations);
				if (res != null) {
					for (int k = 0; k < res.length; k++) {
						fProposals.add(res[k]);
					}
				}
			}
		}
	}

	private static class SafeHasAssist extends SafeCorrectionProcessorAccess {
		private final IInvocationContext fContext;
		private       boolean            fHasAssists;

		public SafeHasAssist(IInvocationContext context) {
			fContext = context;
			fHasAssists = false;
		}

		public boolean hasAssists() {
			return fHasAssists;
		}

		@Override
		public void safeRun(ContributedProcessorDescriptor desc) throws Exception {
			IQuickAssistProcessor processor =
					(IQuickAssistProcessor)desc.getProcessor(fContext.getCompilationUnit(), IQuickAssistProcessor.class);
			if (processor != null && processor.hasAssists(fContext)) {
				fHasAssists = true;
			}
		}
	}

	private static class SafeHasCorrections extends SafeCorrectionProcessorAccess {
		private final ICompilationUnit fCu;
		private final int              fProblemId;
		private       boolean          fHasCorrections;

		public SafeHasCorrections(ICompilationUnit cu, int problemId) {
			fCu = cu;
			fProblemId = problemId;
			fHasCorrections = false;
		}

		public boolean hasCorrections() {
			return fHasCorrections;
		}

		@Override
		public void safeRun(ContributedProcessorDescriptor desc) throws Exception {
			IQuickFixProcessor processor = (IQuickFixProcessor)desc.getProcessor(fCu, IQuickFixProcessor.class);
			if (processor != null && processor.hasCorrections(fCu, fProblemId)) {
				fHasCorrections = true;
			}
		}
	}


	public static IStatus collectCorrections(IInvocationContext context, IProblemLocation[] locations,
											 Collection<IJavaCompletionProposal> proposals) {
		ContributedProcessorDescriptor[] processors = getCorrectionProcessors();
		SafeCorrectionCollector collector = new SafeCorrectionCollector(context, proposals);
		for (int i = 0; i < processors.length; i++) {
			ContributedProcessorDescriptor curr = processors[i];
			IProblemLocation[] handled = getHandledProblems(locations, curr);
			if (handled != null) {
				collector.setProblemLocations(handled);
				collector.process(curr);
			}
		}
		return collector.getStatus();
	}

	private static IProblemLocation[] getHandledProblems(IProblemLocation[] locations, ContributedProcessorDescriptor processor) {
		// implementation tries to avoid creating a new array
		boolean allHandled = true;
		ArrayList<IProblemLocation> res = null;
		for (int i = 0; i < locations.length; i++) {
			IProblemLocation curr = locations[i];
			if (processor.canHandleMarkerType(curr.getMarkerType())) {
				if (!allHandled) { // first handled problem
					if (res == null) {
						res = new ArrayList<IProblemLocation>(locations.length - i);
					}
					res.add(curr);
				}
			} else if (allHandled) {
				if (i > 0) { // first non handled problem
					res = new ArrayList<IProblemLocation>(locations.length - i);
					for (int k = 0; k < i; k++) {
						res.add(locations[k]);
					}
				}
				allHandled = false;
			}
		}
		if (allHandled) {
			return locations;
		}
		if (res == null) {
			return null;
		}
		return res.toArray(new IProblemLocation[res.size()]);
	}

	public static IStatus collectAssists(IInvocationContext context, IProblemLocation[] locations,
										 Collection<IJavaCompletionProposal> proposals) {
		ContributedProcessorDescriptor[] processors = getAssistProcessors();
		SafeAssistCollector collector = new SafeAssistCollector(context, locations, proposals);
		collector.process(processors);

		return collector.getStatus();
	}

	/*
	 * @see IContentAssistProcessor#getErrorMessage()
	 */
	public String getErrorMessage() {
		return fErrorMessage;
	}

	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canFix(org.eclipse.jface.text.source.Annotation)
	 * @since 3.2
	 */
	public boolean canFix(Annotation annotation) {
		return hasCorrections(annotation);
	}

	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canAssist(org.eclipse.jface.text.quickassist
	 * .IQuickAssistInvocationContext)
	 * @since 3.2
	 */
	public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
		if (invocationContext instanceof IInvocationContext)
			return hasAssists((IInvocationContext)invocationContext);
		return false;
	}

}
